﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Web;
using System.Linq;
using System.Text;

namespace JQuery
{
    public partial class Resource
    {
        public class Content
        {
            #region properties
            public string key { get { return owner.GetResourceKey(id, type); } }
            public string id { get; private set; }
            public string uri { get; private set; }
            public string type { get; private set; }
            public Assembly assembly { get; private set; }
            public int ordinal { get; set; }
            internal Resource owner { get; set; }
            #endregion

            public Content(String id, String uri, String type, Assembly assembly)
            {
                if (id.IsNullOrEmpty())
                    throw new ArgumentNullException("id");

                this.id = id;
                this.uri = uri;
                this.type = type;
                this.assembly = assembly;
            }

            internal bool Body(StreamWriter writer)
            {
                bool success = true;

                if (!success)   // we first check the resource manager to make certain that the key is not defined
                    return false;
                try
                {
                    HttpApplication TheApp = owner.TheApp;

                    if (uri.StartsWith("~") || uri.IndexOf("://") != -1)
                    {
                        string path = TheApp.Server.MapPath(TheApp.ResolveUrl(uri));

                        if (!File.Exists(path))
                        {
                            return false;
                        }

                        using (StreamReader oFile = new StreamReader(File.OpenRead(path)))
                        {
                            writer.Write(oFile.ReadToEnd());
                        }
                    }
                    else
                    {   // embedded resources
                        Uri script = new Uri(String.Format("file:///{0}", uri));

                        using (StreamReader oFile = new StreamReader(assembly.GetManifestResourceStream(Path.GetFileName(script.AbsolutePath))))
                        {
                            writer.Write(oFile.ReadToEnd());
                        }
                    }
                }
                catch
                {
                    success = false;
                }

                return success;
            }
        }

        #region properties
        public HttpApplication TheApp { get; internal set; }
        public bool GeneratedScriptPathExists
        {
            get
            {
                return Directory.Exists(TheApp.Server.MapPath(TheApp.ResolveUrl(GeneratedScriptPath)));
            }
        }

        public System.Web.Caching.Cache Cache
        {
            get
            {
                return HttpContext.Current.Cache;
            }
        }

        public bool GeneratedCssPathExists
        {
            get
            {
                return Directory.Exists(TheApp.Server.MapPath(TheApp.ResolveUrl(GeneratedCssPath)));
            }
        }
        public string GeneratedScriptPath
        {
            get
            {
                return String.Format("{0}/generated", Config.ScriptFolder);
            }
        }
        public string GeneratedCssPath
        {
            get
            {   /// one thing to not worry about is that css files will be virtually mapped to the css folder or selected app_themes folder so the image references 
                /// will be maintaned. this path is used to keep the file system organized.
                return String.Format("{0}/generated", Config.CssFolder);
            }
        }
        #endregion

        public Resource.Content this[string key]
        {
            get { return Resources.ContainsKey(key) ? Resources[key] : null; }
        }

        private Resource(HttpApplication App)
        {
            Resources = new ResourceManager();
            TheApp = App;
        }

        internal ResourceManager Resources;

        /// <summary>
        /// Initialize the resource sub system
        /// </summary>
        /// <returns></returns>
        internal static Resource Initialize(HttpApplication App)
        {
            Resource oSystem = new Resource(App);

            oSystem.Add("api", String.Format("{0}/jquery-{1}.js", Config.ScriptFolder, Config.JQueryAPI), ContentTypes.javascript);
            
            if (Config.IsJQueryUIAvailble)
            {   // we need to iterate the ui folder found in the script_folder and add all the script files found
                string folder = App.MapFilePath(String.Format("{0}/ui", Config.ScriptFolder));
                // now we iterate the files
                foreach (string File in Directory.GetFiles(folder))
                {
                    string
                        Tmp = File.Substring(File.LastIndexOf('\\') + 1),
                        key = Tmp.Substring(0, Tmp.Length - 3).Replace("jquery-", "").Replace("jquery.", ""),
                        uri = String.Format("{0}/ui/{1}", Config.ScriptFolder, Tmp);

                    oSystem.Add(key, uri, ContentTypes.javascript);
                }
                // process localization files
                folder = App.MapFilePath(String.Format("{0}/ui/i18n", Config.ScriptFolder));
                foreach (string File in Directory.GetFiles(folder))
                {
                    string
                        Tmp = File.Substring(File.LastIndexOf('\\') + 1),
                        key = Tmp.Substring(0, Tmp.Length - 3).Replace("jquery-", "").Replace("jquery.", ""),
                        uri = String.Format("{0}/ui/i18n/{1}", Config.ScriptFolder, Tmp);

                    if (key.Eq("ui-i18n")) continue; // we ignore this file since when in production we will be replicating the benefit of one file.

                    if (Config.LoadAllLanguageFiles || Config.Localization.Any(x => key.EndsWith(x)))
                    {   // register the language file
                        oSystem.Add(key, uri, ContentTypes.javascript);
                    }
                }
                // now we need to register the css files associated with the jquery ui
                folder = App.MapFilePath(Config.CssFolder);
                foreach (string File in Directory.GetFiles(folder))
                {
                    string
                        Tmp = File.Substring(File.LastIndexOf('\\') + 1),
                        key = Tmp.Substring(0, Tmp.Length - 4).Replace("jquery-", "").Replace("jquery.", ""),
                        uri = String.Format("{0}/{1}", Config.CssFolder, Tmp);

                    if (key.Eq("ui") || key.Eq("ui.all") || key.Eq("ui.base") || ((!Config.MVCEnabled || !Config.CSSThemeOverride) && key.Eq("ui.theme"))) continue; 
                    // We ignore this file since when in production we will be replicating the benefit of one file.
                    // In asp.net application the theme css file should technically be located in the app_themes folder.
                    // In the event that app_themes is not being used, we have provided a way to override the behavior.

                    oSystem.Add(key, uri, ContentTypes.css);
                }
            }

            oSystem.Add("net", Scripts.net, ContentTypes.javascript);

            return oSystem;
        }

        public void Add(string id, string uri, string type, int ordinal = 0, Assembly assembly = null)
        {
            if (!Resources.ContainsKey(GetResourceKey(id, type)))
            {
                if(assembly == null)
                {
                    assembly = Assembly.GetExecutingAssembly();
                }
                Resources.Add(new Content(id, uri, type, assembly) { ordinal = SetOrdinal(id, ordinal), owner = this });
            }
        }

        public string GetResourceKey(string id, string type = "text/javascript")
        {
            return String.Format("{0}:{1}", id, type);
        }

        public bool Remove(string id, string type = "text/javascript")
        {
            string key = GetResourceKey(id, type);

            if (Resources.ContainsKey(key))
            {
                return Resources.Remove(key);
            }

            return false;
        }

        private int SetOrdinal(string id, int ordinal)
        {
            switch(id.ToLower())
            {
                case "api":
                    return -4;
                case "ui.core":
                case "ui.position":
                case "ui.widget":
                    return -3;
                case "ui.mouse":
                    return -2;
                case "net":
                    return -1;
                default:
                    return ordinal < 1 ? Resources.Max(x => x.Value.ordinal) + 1 : ordinal;
            }
        }

        internal Resource.Content GeneratedContent(string id, string type, bool generated = true)
        {
            switch(type)
            {
                default:
                    throw new NotSupportedException(String.Format("Content type '{0}', can not be generated on the fly.", type));
                case "text/javascript":
                    return new Content(id, String.Format("{0}/{1}.js", generated ? GeneratedScriptPath : Config.ScriptFolder, id), type, null);
                case "text/css":
                    return new Content(id, String.Format("{0}/{1}.js", generated ? GeneratedCssPath : Config.CssFolder, id), type, null);
            }
        }

        internal bool Fetch(string id, string type, HttpResponse response)
        {
            bool success = true;

            string key = GetResourceKey(id, type);

            if (!success && !Resources.ContainsKey(key))   // we first check the resource manager to make certain that the key is not defined
                return false;

            try
            {
                Resource.Content oItem = Resources[key] ?? ((PageManager)TheApp.Application["pages"]).IsNotNull(GeneratedContent(id, type), manager => manager[id][key]);
                // set the content type
                response.ContentType = oItem.type;

                byte[] 
                    content = null,
                    minimized = null;

                UTF8Encoding oUTF = new UTF8Encoding(true);

                if (oItem.uri.StartsWith("~") || oItem.uri.IndexOf("://") != -1)
                {
                    string path = TheApp.MapFilePath(oItem.uri);

                    if (!File.Exists(path))
                    {   /// before giving up check the script folder
                        oItem = GeneratedContent(id, type, false);

                        path = TheApp.MapFilePath(oItem.uri);

                        if (!File.Exists(path))
                        {
                            return false;
                        }
                    }

                    using (StreamReader Reader = new StreamReader(File.OpenRead(path)))
                    {
                        if (!Config.IsMinimized)
                        {
                            response.Write(Reader.ReadToEnd());
                            // we are done and can exit the method
                            return success;
                        }

                        content = oUTF.GetBytes(Reader.ReadToEnd());
                    }
                }
                else
                {   // embedded resources
                    Uri script = new Uri(String.Format("file:///{0}", oItem.uri));

                    using (StreamReader Reader = new StreamReader(oItem.assembly.GetManifestResourceStream(Path.GetFileName(script.AbsolutePath))))
                    {
                        if (!Config.IsMinimized)
                        {
                            response.Write(Reader.ReadToEnd());
                            // we are done and can exit the method
                            return success;
                        }

                        content = oUTF.GetBytes(Reader.ReadToEnd());
                    }
                }

                if(oItem.type.Eq(ContentTypes.javascript))
                {
                    using(JSMinimizer oMinimizer = new JSMinimizer(content, oItem.type))
                    {
                        minimized = oMinimizer.ToArray();
                    }
                }
                else if (oItem.type.Eq(ContentTypes.css))
                {
                    using (CSSMinimizer oMinimizer = new CSSMinimizer(content, oItem.type))
                    {
                        minimized = oMinimizer.ToArray();
                    }
                }

                response.BinaryWrite(minimized);
            }
            catch(Exception ex)
            {
                Utilities.Utility.TraceEvent("resource:fetch", ex, Utilities.TraceSeverity.Critical);
                success = false;
            }

            return success;
        }
    }
}
